#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/mod_devicetable.h>
#include <linux/log2.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/acpi.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>

#define Read_oled   100
#define Write_cmd  	101
#define Write_data  102

static int major = 0;
static struct class* oled_class;
static struct i2c_client *oled_client;

static int oled_i2c_read_byte(unsigned char reg, unsigned char *rx_buf)
{
    int ret;

    struct i2c_msg oled_msg[] = {
        {
            .addr   = oled_client -> addr,
            .flags  = 0,                         //write
            .len    = 1,
            .buf    = &reg,
        },
        {
            .addr   = oled_client -> addr,
            .flags  = 1,
            .len    = 1,
            .buf    = rx_buf,
        }
    };
    ret = i2c_transfer(oled_client -> adapter, oled_msg, 2);
    if(ret != 2) {
        return -1;
    }
    return ret;
}

static int oled_i2c_write_byte(unsigned char *tx_buf, unsigned int len)
{
    int ret;
    struct i2c_msg oled_msg[1];

    oled_msg[0].addr =  oled_client -> addr;
    oled_msg[0].flags = 0;
    oled_msg[0].len = len;
    oled_msg[0].buf = tx_buf;

    ret = i2c_transfer(oled_client -> adapter, oled_msg, 1);
    
    if(ret != 1) {
        printk("i2c_transfer err = %d addr = %x!\n",ret, oled_msg[0].addr);
        return -1;
    }
    return ret;
}
/*  当ioctl函数的第三个参数直接传递值时，不需要使用copy_from_user，
    因为此时值已经在内核空间中，可以直接使用。但如果ioctl函数的第三
    个参数传递的是地址，即传递的是用户空间的地址，那么就需要使用
    copy_from_user来将用户空间的数据拷贝到内核空间。这是因为内核和
    用户空间是隔离的，直接访问用户空间的内存是不安全的。 */
static long oled_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    unsigned char byte_buf[2];
    unsigned char rx_buf;

    switch (cmd)
    {
    case Read_oled:
        oled_i2c_read_byte(0, &rx_buf);
        break;
    case Write_cmd:
        byte_buf[0] = 0x00;
        byte_buf[1] = (unsigned char)arg;
        oled_i2c_write_byte(byte_buf, 2);
        break;   
    case Write_data:
        byte_buf[0] = 0x40;
        byte_buf[1] = (unsigned char)arg;
        oled_i2c_write_byte(byte_buf, 2);
        break;           
    }
    return 0;
}

static struct file_operations oled_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = oled_ioctl,
};

static struct of_device_id pbs_oled_match[] = {
    {.compatible = "PB, oled"},
	{}
};

static struct i2c_device_id oled_id_table[] = {
	{ "pbs_oled", (kernel_ulong_t)NULL },
	{}
};

static int oled_probe(struct i2c_client *client, const struct i2c_device_id *id) 
{
    oled_client = client;
	/* register_chrdev */
	major = register_chrdev(0, "oled", &oled_fops);  
	/* class_create */
	oled_class = class_create(THIS_MODULE, "oled_class");
	/* device_create */
	device_create(oled_class, NULL, MKDEV(major, 0), NULL, "pb@oled");

    return 0;
}

static int oled_remove(struct i2c_client *client) 
{
    device_destroy(oled_class, MKDEV(major, 0));
    class_destroy(oled_class);
    unregister_chrdev(major, "oled");
    
    return 0;
}

static struct i2c_driver oled_i2c_driver = {
    .driver = {
        .name = "oled",
        .of_match_table = pbs_oled_match,
    },
    .probe = oled_probe,
    .remove = oled_remove,
    .id_table = oled_id_table,
};

static int oled_init(void)
{
	int err;
	err = i2c_add_driver(&oled_i2c_driver);
	printk("%s %s %d, err = %d\n", __FILE__, __FUNCTION__, __LINE__, err);
	return err;
}

static void oled_exit(void)
{
	i2c_del_driver(&oled_i2c_driver);
}

module_init(oled_init);
module_exit(oled_exit);

MODULE_LICENSE("GPL");